#include <cassert>
#include <sstream>
#include <string>

#include "base/compiler.hh"
#include "base/cprintf.hh"
#include "debug/RubyGenerated.hh"
#include "debug/RubySlicc.hh"
#include "mem/ruby/common/Global.hh"
#include "mem/ruby/QOS/QOS_Agent.hh"
#include "mem/ruby/slicc_interface/RubySlicc_includes.hh"
#include "mem/ruby/system/System.hh"

using namespace std;
#include "mem/protocol/MessageBuffer.hh"

/** \brief constructor */
QOS_Agent::QOS_Agent(Network* own_net, Switch* own_switch)
    : Consumer()
{
    m_net_ptr = own_net;
    m_switch_ptr = own_switch;
    m_virtual_networks = m_net_ptr->getNumberOfVirtualNetworks();

    m_packets_per_tick = 16;
    m_credits_replenish = 4;
    m_credits_max = 32;
    
    m_credits = 0;
}

void
QOS_Agent::print(std::ostream&) const
{
    return;
}

void
QOS_Agent::addInPort(const vector<MessageBuffer*>& in)
{
    m_in = in;

    for(int i = 0; i < m_virtual_networks; i++)
    {
        m_in[i]->setConsumer(this);
        m_in[i]->setIncomingLink(0);
        m_in[i]->setVnet(i);
    }
}

void
QOS_Agent::addOutPort(const vector<MessageBuffer*>& out)
{
    m_out = out;
    for(int i = 0; i < m_virtual_networks; i++)
    {
        m_out[i]->setOrdering(true);
    }
}

void
QOS_Agent::wakeup()
{
    double qos_ad = m_switch_ptr->getQoSAdmission();
    m_credits += m_credits_replenish * qos_ad;
    m_credits = (m_credits > m_credits_max) ? (m_credits_max) : (m_credits);

    int cur_time = g_eventQueue_ptr->getTime();

    // Do not flow control packets not in the request virtual network
    for(int i = 1; i < m_virtual_networks; i++)
    {
        while(m_in[i]->isReady())
        {
            MsgPtr forwarding;
            m_in[i]->dequeue(forwarding);
            forwarding->deadline = cur_time;
            m_out[i]->enqueue(forwarding);
        }
    }

    // flow control the requests virtual network
    int serviced_packets = 0;
    while((m_in[0]->isReady()) && (serviced_packets < m_packets_per_tick))
    {
        serviced_packets++;

        MsgPtr current;
        m_in[0]->dequeue(current);

        current->deadline = cur_time;

        m_out[0]->enqueue(current);
    }

    g_eventQueue_ptr->scheduleEvent(this, 1);
}
